﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Corecalc;

namespace Sisal
{
    public class GRGraph
    {
        private bool Dependency { get; set; }
        public GRNode Root { get; set; }
        public List<GRNode> Nodes { get; set; }
        public List<GRNode> FormulaNodes { get; set; }
        public Dictionary<GRNode, List<GRNode>> RCG { get; set; }

        public GRGraph(GRNode root, List<GRNode> formulaNodes, bool dependency) {
            Root = root;
            Dependency = dependency;
            Nodes = formulaNodes.SelectMany(fn => fn.GetLocalGraph()).ToList();

            //If the root is a value node and it delivers its output to more than one formula
            //then those formulas will return the the root in boths its GetLocalGraph() calls, so its
            //present mulitple times in the list
            if (!Root.IsFormulaNode && Root.OutputEdges.Count > 1)
                Nodes = Nodes.Distinct().ToList();

            RCG = GetRCG(formulaNodes);
            FormulaNodes = Procedures.TopSort(RCG).ToList();
        }

        private Dictionary<GRNode, List<GRNode>> GetRCG(List<GRNode> nodes) {
            Dictionary<GRNode, List<GRNode>> rcg = new Dictionary<GRNode, List<GRNode>>();

            foreach (GRNode node in nodes) {
                rcg.Add(node, GetReachableFormulaNodes(node, new List<GRNode>()));
            }

            return rcg;
        }

        private List<GRNode> GetReachableFormulaNodes(GRNode sn, List<GRNode> nodes) {
            List<GRNode> edges = Dependency ? sn.InputEdges : sn.OutputEdges;

            if (edges.Count() == 0) {
                return nodes;
            } else {
                foreach (GRNode node in edges) {
                    if (node.IsFormulaNode) {
                        nodes.Add(node);
                    }

                    GetReachableFormulaNodes(node, nodes);
                }

                return nodes;
            }
        }

        public void PrintGraph() {
            Console.WriteLine("- - - - - - - - - - TP Sorted Nodes - - - - - - - - - - - - ");
            IEnumerable<GRNode> topSortedNodes = Procedures.TopSort(Nodes, n => n.OutputEdges);
            foreach (var node in topSortedNodes) {
                Console.WriteLine(node);
            }
            Console.WriteLine();

            Console.WriteLine("- - - - - - - - - - Nodes - - - - - - - - - - - -");
            foreach (GRNode node in Nodes) {
                Console.WriteLine("{0}", node);

                //if(node.InputEdges.Count > 0)
                //    Console.WriteLine();
                foreach (GRNode inpNode in node.InputEdges) {
                    Console.WriteLine("\t\t\tInput from:\t {0}", inpNode);
                }

                //if (node.OutputEdges.Count > 0)
                //    Console.WriteLine();
                foreach (GRNode outNode in node.OutputEdges) {
                    Console.WriteLine("\t\t\tOutput to:\t {0}", outNode);
                }

                Console.WriteLine();
            }

            Console.WriteLine("- - - - - - - - - - FormulaNodes - - - - - - - - - - - -");
            var formulaNodes = Nodes.Where(x => x.IsFormulaNode);
            foreach (GRNode fn in formulaNodes) {
                Console.WriteLine("{0}[{1}, {2}]: {3}", fn.Fca.sheet, fn.Fca.ca.col, fn.Fca.ca.row, fn);
            }

            //Console.WriteLine("- - - - - - - - - - - - - - - - - ");
            //var ffffff = Nodes.Where(x => x is FormulaNode).First();
            //List<SisalNode> ns = ffffff.GetNodes();
            //foreach (SisalNode node in ns) {
            //    Console.WriteLine("{0}: {1}", node, node.ExecutionTime);
            //}
        }
    }

    public class SupportGraph
    {
        public List<FullCellAddr> Nodes { get; set; }
        public List<SupportEdge> Edges { get; set; }

        public SupportGraph() {
            Nodes = new List<FullCellAddr>();
            Edges = new List<SupportEdge>();
        }
    }

    public class SupportEdge
    {
        public FullCellAddr FromNode { get; set; }   
        public FullCellAddr ToNode { get; set; }     
    }

    public static partial class Procedures
    {
        public static SupportGraph BuildGRSupportGraph(this Workbook wb) {
            SupportGraph graph = new SupportGraph();

            for (int sc = 0; sc < wb.SheetCount; sc++) {
                Sheet sheet = wb[sc];

                for (int column = 0; column < sheet.Cols; column++) {
                    for (int row = 0; row < sheet.Rows; row++) {
                        Cell referredCell = sheet[column, row];
                        FullCellAddr referredFca = new FullCellAddr(sheet, new CellAddr(column, row));

                        if (referredCell != null) {
                            if (!graph.Nodes.Contains(referredFca)) {
                                graph.Nodes.Add(referredFca);
                            }

                            referredCell.ForEachSupported((s, c, r) => {
                                FullCellAddr supportedFca = new FullCellAddr(s, new CellAddr(c, r));

                                if (!graph.Nodes.Contains(supportedFca)) {
                                    graph.Nodes.Add(supportedFca);
                                }

                                graph.Edges.Add(new SupportEdge {
                                    FromNode = referredFca,
                                    ToNode = supportedFca
                                });
                            });
                        }
                    }
                }
            }

            return graph;
        }

        //public static SisalGraph Expand(this SupportGraph g) {
            //List<SisalNode> nodes = new List<SisalNode>();
            //Dictionary<FullCellAddr, SisalNode> seenBefore = new Dictionary<FullCellAddr, SisalNode>();

            //foreach (SupportEdge edge in g.Edges) {
            //    //From node (FromNode supports ToNode)
            //    Sheet fromSheet = edge.FromNode.sheet;
            //    int fromCol = edge.FromNode.ca.col;
            //    int fromRow = edge.FromNode.ca.row;
            //    Cell cell = fromSheet[fromCol, fromRow];

            //    if (!(cell is ConstCell)) {
            //        SisalNode fromNode = cell.Expand(fromSheet, fromCol, fromRow);
            //        //NB: Set parent!

            //        FullCellAddr fromFca = new FullCellAddr(fromSheet, edge.FromNode.ca);

            //        if (!seenBefore.ContainsKey(fromFca)) {
            //            seenBefore.Add(fromFca, fromNode);
            //            nodes.AddRange(fromNode.GetGraph());
            //        }
            //    }

            //    //To node (ToNode refers to FromNode)
            //    Sheet toSheet = edge.ToNode.sheet;
            //    int toCol = edge.ToNode.ca.col;
            //    int toRow = edge.ToNode.ca.row;
            //    SisalNode toNode = toSheet[toCol, toRow].Expand(toSheet, toCol, toRow);
            //    //NB: Set parent!

            //    FullCellAddr toFca = new FullCellAddr(toSheet, edge.ToNode.ca);

            //    if (!seenBefore.ContainsKey(toFca)) {
            //        seenBefore.Add(toFca, toNode);
            //        nodes.AddRange(toNode.GetGraph());
            //    }
            //}


            //return new SisalGraph(nodes);
        //    var vn = new ValueNode<double>(0.0, 0);
        //    return new SisalGraph(vn, true);
        //}
    }
}
